/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { HvigorNode } from '@ohos/hvigor';
import { Target } from '@ohos/hvigor-ohos-plugin';
import { OhosModuleContext } from '@ohos/hvigor-ohos-plugin/src/plugin/context/plugin-context';
import { TaskManager } from './TaskManager';
import { TaskStage } from './TaskStage';
import { TaskConstants } from '../constants';
import { ExtensionContextImpl, PluginExtension } from '../extension';
import { isHapModule, isHspModule, PluginFileUtil } from '../utils';

/**
 * Plugin execution controller
 *
 * This class is the main execution controller for the plugin.
 * Each module has its own
 * controller instance that manages the execution flow of plugin extensions and tasks.
 * It acts as the entry point and flow controller for the entire framework.
 *
 * The controller implements ExtensionContextProvider to provide specialized context
 * objects for each extension, enabling data isolation between extensions.
 */
export class PluginExecutionController {
  private readonly node: HvigorNode;
  private readonly moduleContext: OhosModuleContext;
  private readonly taskManager: TaskManager;
  private readonly baseContext: ExtensionContextImpl;

  // Configuration backup for dynamic adjustment
  private originalBuilderDir?: string;

  /**
   * Constructs a new PluginExecutionController
   *
   * @param {HvigorNode} node - The Hvigor node representing a module
   * @param {OhosModuleContext} moduleContext - The module context from Hvigor
   * @param {PluginExtension[]} [moduleExtensions] - Optional array of custom extensions for this module
   */
  constructor(
    node: HvigorNode,
    moduleContext: OhosModuleContext,
    moduleIgnored: boolean,
    moduleExtensions?: PluginExtension[]
  ) {
    this.node = node;
    this.moduleContext = moduleContext;
    this.baseContext = this.initializeContext();
    this.baseContext.moduleIgnored = moduleIgnored;
    this.taskManager = new TaskManager(this.baseContext, moduleExtensions);
  }

  start(): void {
    this.taskManager.executeStage(TaskStage.AFTER_INITIALIZE);
    this.registerHvigorTasks();
  }

  complete(): void {
    this.taskManager.executeStage(TaskStage.AFTER_COMPLETION);
  }

  private initializeContext(): ExtensionContextImpl {
    const context = new ExtensionContextImpl(this.node, this.moduleContext);
    context.scanFiles = [];
    return context;
  }

  /**
   * Registers all required Hvigor tasks for the build process
   *
   * This method sets up all the build stages that'll be executed during
   * the Hvigor build process, including
   * - Annotation analysis
   * - Code generation
   * - Router map building
   * - Configuration updates
   * - Obfuscation
   * - Resource processing
   *
   * @private
   */
  private registerHvigorTasks(): void {
    this.moduleContext.targets((target: Target) => {
      const targetName = target.getTargetName();

      // Register all tasks for this target
      this.registerMainPluginTask(target);
      this.registerObfuscationTask(targetName);
      if (isHapModule(this.node) || isHspModule(this.node)) {
        this.registerCopyRouterMapTask(target);
      }
    });
  }

  /**
   * Filters scan files based on current target's source roots
   *
   * This method converts relative sourceRoots to absolute paths and filters
   * scanFiles to only include files that belong to the current target.
   * It handles cross-platform path separators and ensures accurate path matching.
   *
   * @param currentTargetName
   * @private
   */
  private filterScanFilesForTarget(currentTargetName: string): string[] {
    const buildProfileTargets = this.moduleContext.getBuildProfileOpt().targets || [];
    const targetOpt = buildProfileTargets.find((target) => target.name === currentTargetName);

    let absoluteSourceRoots: string[];

    // Early return if no target config or sourceRoots found
    if (!targetOpt?.source?.sourceRoots || targetOpt.source.sourceRoots.length === 0) {
      // use defaule path
      absoluteSourceRoots = this.getAbsoluteSourceRoots([TaskConstants.DEFAULT_SOURCE_ROOT]);
    } else {
      if (!targetOpt.source.sourceRoots.find((value) => value === TaskConstants.DEFAULT_SOURCE_ROOT)) {
        targetOpt.source.sourceRoots.push(TaskConstants.DEFAULT_SOURCE_ROOT);
      }
      // Convert all sourceRoots to normalized absolute paths
      absoluteSourceRoots = this.getAbsoluteSourceRoots(targetOpt.source.sourceRoots);
    }

    // 使用模块级主扫描文件避免跨目标突变
    const masterFiles = (this.baseContext as any).getModuleScanFiles() as string[];
    // Filter scanFiles to only include files within the target's sourceRoots
    return masterFiles.filter((filePath) => {
      const normalizedFilePath = PluginFileUtil.normalize(filePath);

      return absoluteSourceRoots.some((sourceRootPath: string) => {
        return normalizedFilePath.startsWith(sourceRootPath);
      });
    });
  }

  private getAbsoluteSourceRoots(sourceRoots: string[]): string[] {
    // Get module root path for resolving relative paths
    const moduleRoot = this.moduleContext.getModulePath();
    return sourceRoots.map((sourceRoot: string) => {
      const absolutePath = PluginFileUtil.resolve(moduleRoot, sourceRoot);
      const normalizedPath = PluginFileUtil.normalize(absolutePath);
      // Ensure path ends with separator for accurate prefix matching
      return normalizedPath.endsWith(PluginFileUtil.sep) ? normalizedPath : normalizedPath + PluginFileUtil.sep;
    });
  }

  /**
   * Registers the main plugin task
   *
   * @param target - Target object
   * @private
   */
  private registerMainPluginTask(target: Target): void {
    const targetName = target.getTargetName();
    this.node.registerTask({
      name: this.getTaskName(targetName, TaskConstants.PLUGIN_TASK),
      run: () => {
        // 将当前目标绑定到每个目标作用域状态的上下文
        this.taskManager.context.currentTarget = target;

        // 从模块主列表中筛选此目标的扫描文件，并分配给每个目标存储
        const filtered = this.filterScanFilesForTarget(targetName);
        this.baseContext.scanFiles = filtered;

        this.taskManager.executeStage(TaskStage.AFTER_ANNOTATION_ANALYSIS);
        this.taskManager.executeStage(TaskStage.AFTER_CODE_GENERATION);
        this.taskManager.executeStage(TaskStage.AFTER_ROUTER_MAP_BUILDING);
        this.taskManager.executeStage(TaskStage.AFTER_CONFIG_UPDATE);
      },
      dependencies: [],
      postDependencies: [this.getTaskName(targetName, TaskConstants.PRE_BUILD)],
    });
  }

  /**
   * Registers the obfuscation task
   *
   * @param targetName - Name of the target
   * @private
   */
  private registerObfuscationTask(targetName: string): void {
    this.node.registerTask({
      name: this.getTaskName(targetName, TaskConstants.GENERATE_OBFUSCATION_TASK),
      run: () => {
        this.taskManager.executeStage(TaskStage.AFTER_OBFUSCATION_PROCESS);
      },
      dependencies: [this.getTaskName(targetName, TaskConstants.PLUGIN_TASK)],
      postDependencies: [this.getTaskName(targetName, TaskConstants.PRE_BUILD)],
    });
  }

  /**
   * Registers the copy router map task
   *
   * @param targetName - Name of the target
   * @private
   */
  private registerCopyRouterMapTask(target: Target): void {
    const targetName = target.getTargetName();
    this.node.registerTask({
      name: this.getTaskName(targetName, TaskConstants.COPY_ROUTER_MAP_TASK),
      run: () => {
        this.taskManager.context.currentTarget = target;
        this.taskManager.executeStage(TaskStage.AFTER_RESOURCE_PROCESS);
      },
      dependencies: [this.getTaskName(targetName, TaskConstants.PROCESS_ROUTER_MAP)],
      postDependencies: [this.getTaskName(targetName, TaskConstants.PROCESS_RESOURCE)],
    });
  }

  /**
   * Generates task name by combining target name and task constant
   *
   * @param targetName - Name of the target
   * @param taskConstant - Task constant from TaskConstants
   * @returns Combined task name
   * @private
   */
  private getTaskName(targetName: string, taskConstant: string): string {
    return targetName + taskConstant;
  }
}
